Borrowed pointers | 借贷指针
原文: https://github.com/nrc/r4cppp/blob/master/borrowed.md 翻译者: Scott Huang 翻译日期: Agust 26, 2015 于厦门
在上一篇文章中,我介绍了独特的指针(所有权指针)。这一次我会谈谈另一种更常见于大多数Rust程序的指针:借贷指针(或借用的引用,或只是引用)。
如果我们想有一个引用指向现有的值(而不是创建一个在堆上的新值并指向它,作为所有权指针),我们必须使用&
,一个借来的引用。这可能是Rust最常见的一种指针,如果你想用某些东西来替代一个C++指针或引用(例如,通过引用传递函数的参数),这可能就是它。
我们使用&
操作符创建一个借来的引用,并表示参考类型,并用*
来解引用。自动解引用规则也应用于借贷指针,和所有权指针一样。例如,
fn foo() {
let x = &3; // type: &i32 类型: &i32
let y = *x; // 3, type: i32 3, 类型: i32
bar(x, *x);
bar(&y, y);
}
fn bar(z: &i32, i: i32) {
// ...
}
&
操作符不分配内存(我们只创建一个借贷指针来引用现有值),如果借贷指针超出了使用范围,没有内存被删除。
借用的引用不是独一无二的-你可以有多个借来的引用指向同一值。例如,
fn foo() {
let x = 5; // type: i32
let y = &x; // type: &i32
let z = y; // type: &i32
let w = y; // type: &i32
println!("These should all 5: {} {} {}", *w, *y, *z);
}
就像值,借用的引用是默认不可变的。你也可以使用&mut
来获取可变的引用,或表示可变的引用类型。
可变借贷引用是独一无二的(你只可以有一个唯一的可变引用指向值,你也只能有一个可变的引用如果没有
不可改变的引用的话)。你可以使用可变的引用假设只需要一个不可变引用,但不是反之亦然。把所有的这些概念放在下面这个例子:
fn bar(x: &i32) { ... }
fn bar_mut(x: &mut i32) { ... } // &mut i32 是一个指向i32的引用,并且可以改变
fn foo() {
let x = 5;
//let xr = &mut x; // 错误 - 无法从一个不可变的变量上创建一个可变的引用
let xr = &x; // 可以创建一个不可变的引用
bar(xr);
//bar_mut(xr); // 错误,期望一个可变的引用
let mut x = 5;
let xr = &x; // 可以,创建出一个不可变的引用
//*xr = 4; // 错误,试图改变不可变引用
//let xr = &mut x; // 错误 - 已经有一个不可变的引用,所以没有办法同时创建一个可变的引用
let mut x = 5;
let xr = &mut x; // 可以,创建一个可变的引用
*xr = 4; // Ok
//let xr = &x; // 错误 - 已经有一个可变的引用,所以不能同时创建一个不可变的引用
//let xr = &mut x; // 错误 - 同时只能有一个可变的引用。
bar(xr); // Ok
bar_mut(xr); // Ok
}
注意,引用也许可以改变(或不)独立于变量所持有的引用的可变性。这类似于C++的指针可以 是常量(或不)独立的他们所指向的数据。这和所有权指针不同,所有权指针的可变性和数据的可变性是关联在一起的。例如,
fn foo() {
let mut x = 5;
let mut y = 6;
let xr = &mut x;
//xr = &mut y; // 错误, xr是不可变的
let mut x = 5;
let mut y = 6;
let mut xr = &mut x;
xr = &mut y; // Ok
let mut x = 5;
let mut y = 6;
let mut xr = &x;
xr = &y; // 可以 - xr 是可变的,即使所引用的数据不可改变
}
如果一个可变的值是借来的,那么它在借来的期间变成不可变的了。一旦借用的指针超出范围,该值可以被继续改变。这与所有权指针相反,一旦移动就永远不能被再次使用。例如,
fn foo() {
let mut x = 5; // type: i32
{
let y = &x; // type: &i32
//x = 4; // 错误 - x 已经被借用
println!("{}", x); // 可以, x可以读
}
x = 4; // 可以, y已经不存在了
}
如果我们取一个可变引用的值也会发生同样的事情 - 那个值仍不能修改。Rust一般来说,数据只可以通过 一个变量或指针被修改。此外,当我们有一个可变的引用时,我们不能同时取一个不可改变的引用。这限制了我们如何使用底层的值:
fn foo() {
let mut x = 5; // type: i32
{
let y = &mut x; // type: &mut i32
//x = 4; // 错误, x已经被借用了
//println!("{}", x); // 错误, 需要借用x
}
x = 4; // 可以,y已经不存在了
}
与C++不同,Rust不会自动为你的对一个值创建引用。因此,如果一个函数通过引用调用一个参数,调用者必须引用实际参数。但是,指针类型将自动转换为引用:
fn foo(x: &i32) { ... }
fn bar(x: i32, y: Box<i32>) {
foo(&x);
// foo(x); // 错误,期望&i32,但找到i32
foo(y); // 可以
foo(&*y); // 也可以, 并且更显式,但不是一个好的风格
}
mut
vs const
在这个阶段,应该值得比较Rust里的mut
和C++里的const
。
表面上他们是对立的。默认情况下,Rust的值是不可变的,可以通过使用mut
来改变值。C++里默认值是可变的,但可以通过使用const
来常数化。更微妙、更重要的区别
是C++常量性仅适用于当前使用的值,而Rust的不可变性适用于所有值的使用。所以,在C++里如果我有一个const
变量,其他人可以有一个非const引用指向它,他可以改变这个值而不通知我。在Rust里,如果你有一个不可变的变量,Rust会保证这个值不会改变。
正如我们上面提到的,所有可变的变量是独一无二的。所以如果你有一个可变的值,你知道这是不会改变的除非你改变它。此外,你可以自由地改变它,因为你知道,没有任何人正在依靠在它的不变性。
Borrowing and lifetimes 借贷和使用期
Rust的主要安全目标之一是避免悬空指针(一个指针指向一个已释放的内存)。在Rust里,不可能有一个悬空的借贷指针。这是唯一合法的创建借贷指针,只有当所指向内存的存在时间比引用长时(嗯,至少也和引用活的一样长)。换句话说,引用的寿命必须比参考值的寿命要短。
在这篇文章的所有例子中已经展示过。作用域使用{}
被导入或者函数绑定在使用期界限-当一个变量超出范围时,它的寿命结束。如果我们试着把一个引用指向一个短寿命的值,例如
在较窄的范围内,编译器会给我们一个错误。例如,
fn foo() {
let x = 5;
let mut xr = &x; // 可以,x和xr都有相同的使用期
{
let y = 6;
//xr = &y // 错误, xr将比y活的久
} // y在这里被释放
} // x和xr在这里被释放
在上面的例子中,x和XR不具有相同的使用期。因为XR开始的比x晚,但是,使用期的终结点更令人感兴趣,因为你在任何情况下都不能引用一个比引用更早死去的变量-Rust强制执行这些东西,这使得它比C++更 安全。
Explicit lifetimes 显式使用期
玩了一段时间借贷指针后,你可能会遇到一个借来的指针有一个明确的使用期。这些语法是&'a T
(cf
&T
)。它们是一个大主题,因为我需要覆盖使用期-多态性在同一时间,所以我将用另外一篇文章来讨论(有几个更不
常见的指针类型需要先提到)。现在,我只想说&T
是&'a T
的速记,当a
是当前作用域,这是
声明类型的范围。